﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Expression.Encoder.Plugins.Publishing;
using System.Threading;
using System.Diagnostics;
using System.Xml;
using System.Security.Cryptography;
using System.Globalization;
using System.Windows;
using System.IO;
using TimHeuer.Expression.S3Publish.S3Model;
using Affirma.ThreeSharp.Model;

namespace TimHeuer.Expression.S3Publish
{
    [EncoderPlugin("Amazon S3 Publishing Plugin", "Enables publishing to an Amazon S3 bucket")]
    public class Publish : PublishPlugin
    {
        #region Member Variables
        private PublishData _publishData;
        private StandardSettings _standard;
        private string _currentFile = string.Empty;
        private bool _cancelled = false;
        #endregion

        #region Constructors
        public Publish():this(new PublishData())
        {
        }

        public Publish(PublishData data)
        {
            _publishData = data;
        }
        #endregion

        #region Overrides
        public override object CreateAdvancedSettingsEditor()
        {
            return null;
        }

        public override object CreateStandardSettingsEditor()
        {
            if (_standard == null)
            {
                try
                {
                    this._standard = new StandardSettings(_publishData, this);
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                    throw;
                }
            }
            return _standard;
        }

        public override void PerformPublish(string rootPath, string[] filesToPublish)
        {
            int numfiles = filesToPublish.Length;
            int fileProgressSize = 90 / numfiles;
            int currentProgess = 5;

            if (string.IsNullOrEmpty(_publishData.AccessKey) || string.IsNullOrEmpty(_publishData.SecretKey))
            {
                PublishingHost.ShowMessageBox("You need to enter your Amazon S3 account information before proceeding", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                return;
            }

            ThreeSharpWrapper s3 = new ThreeSharpWrapper(this._publishData.AccessKey, this._publishData.SecretKey);
            try
            {
                UpdateProgress("Checking files", currentProgess);
                foreach (var filecheck in filesToPublish)
                {
                    if (Path.GetFileName(filecheck).Contains(" "))
                    {
#if DEBUG
                        MessageBox.Show(filecheck);
#endif
                        PublishingHost.ShowMessageBox(string.Format("One of your files ({0}) contains spaces in the file name.{1}This is not allowed in Amazon S3 storage.{1}{1}To prevent your file from being renamed, please rename it before publishing.", Path.GetFileName(filecheck), Environment.NewLine), MessageBoxButton.OK, MessageBoxImage.Exclamation);
                        return;
                    }
                }
                currentProgess += 5;

                UpdateProgress("Connecting to Amazon S3", currentProgess);

                foreach (var file in filesToPublish)
                {
                    if (!_cancelled)
                    {
                        string contentType = string.Empty;
                        if (Path.GetExtension(file).ToLower() == "xap")
                        {
                            contentType = "application/x-silverlight-app";
                        }

                        _currentFile = file;

                        try
                        {
                            currentProgess += fileProgressSize;
                            UpdateProgress(string.Format("Uploading {0}", Path.GetFileName(file)), currentProgess);
                            s3.AddFileObject(this._publishData.DestinationBucket, GetKeyFromFilePath(file), file, contentType);
                        }
                        catch (S3Model.S3Exception ex)
                        {
                            PublishingHost.ShowMessageBox(ex.Message, MessageBoxButton.OK, MessageBoxImage.Error);
                        }
                        catch (Exception ex)
                        {
                            // enhance
                            PublishingHost.ShowMessageBox(ex.Message, MessageBoxButton.OK, MessageBoxImage.Error);
                        }
                    }
                }

                if (_cancelled)
                {
                    PublishingHost.ShowMessageBox("Upload cancelled", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                }
            }
            catch (Exception ex)
            {
                PublishingHost.ShowMessageBox(ex.Message, MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        protected override void CancelPublish()
        {
            _cancelled = true;
            base.CancelPublish();
        }
        public override void LoadUISettings(System.Xml.XmlReader reader)
        {
            reader.ReadStartElement("AWSUI");
            try
            {
                this._publishData.AccessKey = getValueAsString(reader, "AccessKey");
                this._publishData.SecretKey = DecodeKey(getValueAsString(reader, "SecretKey"));
                this._publishData.MakePublic = getValueAsBool(reader, "MakePublic");
                this._publishData.SaveSecret = getValueAsBool(reader, "SaveSecret");
            }
            catch (Exception)
            {
                // TODO
            }
            reader.ReadEndElement();
        }

        public override void LoadJobSettings(XmlReader reader)
        {
            reader.ReadStartElement("AmazonS3");
            try
            {
                this._publishData.AccessKey = getValueAsString(reader, "AccessKey");
                this._publishData.MakePublic = getValueAsBool(reader, "MakePublic");
            }
            catch (Exception)
            {
                // TODO
            }
            reader.ReadEndElement();
        }
        
        public override void SaveUISettings(System.Xml.XmlWriter writer)
        {
            writer.WriteStartElement("AWSUI");
            writer.WriteElementString("AccessKey", this._publishData.AccessKey);
            writer.WriteElementString("SecretKey", (this._publishData.SaveSecret) ? EncodeKey(this._publishData.SecretKey):string.Empty);
            writer.WriteElementString("MakePublic", this._publishData.MakePublic.ToString());
            writer.WriteEndElement();
        }

        public override void SaveJobSettings(XmlWriter writer)
        {
            writer.WriteStartElement("AmazonS3");
            writer.WriteElementString("AccessKey", this._publishData.AccessKey);
            writer.WriteElementString("MakePublic", this._publishData.MakePublic.ToString());
            writer.WriteEndElement();
        }
        #endregion

        #region Helpers
        internal void UpdateProgress(string description, double progress)
        {
            base.OnProgress(description, progress);
        }
        private static string EncodeKey(string strKey)
        {
            byte[] buffer = ProtectedData.Protect(Encoding.Unicode.GetBytes(strKey), null, DataProtectionScope.CurrentUser);
            StringBuilder builder = new StringBuilder(2 * buffer.Length);
            for (int i = 0; i < buffer.Length; i++)
            {
                builder.AppendFormat(CultureInfo.InvariantCulture, "{0:X2}", new object[] { buffer[i] });
            }
            return builder.ToString();
        }
        private static string DecodeKey(string strEncoding)
        {
            if (string.IsNullOrEmpty(strEncoding))
            {
                return strEncoding;
            }
            if ((strEncoding.Length % 2) != 0)
            {
                return string.Empty;
            }
            byte[] encryptedData = new byte[strEncoding.Length / 2];
            for (int i = 0; i < encryptedData.Length; i++)
            {
                string s = strEncoding.Substring(i * 2, 2);
                byte.Parse(s, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
                if (!byte.TryParse(s, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out encryptedData[i]))
                {
                    return string.Empty;
                }
            }
            try
            {
                byte[] bytes = ProtectedData.Unprotect(encryptedData, null, DataProtectionScope.CurrentUser);
                return Encoding.Unicode.GetString(bytes);
            }
            catch (CryptographicException exception)
            {
                Debug.WriteLine(exception.ToString());
                return string.Empty;
            }
        }
        private static string getValueAsString(XmlReader xr, string ElementName)
        {
            try
            {
                return xr.ReadElementString(ElementName);
            }
            catch (XmlException)
            {
                return "";
            }
        }
        private static bool getValueAsBool(XmlReader xr, string ElementName)
        {
            try
            {
                return bool.Parse(xr.ReadElementString(ElementName));
            }
            catch (XmlException)
            {
                return true;
            }
        }
        internal static string GetKeyFromFilePath(string filePath)
        {
            return GetKeyFromFileName(Path.GetFileName(filePath));
        }

        internal static string GetKeyFromFileName(string fileName)
        {
            if (String.IsNullOrEmpty(fileName))
            {
                return String.Empty;
            }
            return fileName.Replace(" ", "_");
        }
        private class FileUploadData
        {
            public ThreeSharpWrapper Wrapper;
            public S3Bucket Bucket;
            public string Key;
            public string LocalFile;
            public string ContentType;

            public FileUploadData(ThreeSharpWrapper wrapper, S3Bucket bucket, string key, string localFile, string contentType)
            {
                Wrapper = wrapper;
                Bucket = bucket;
                Key = key;
                LocalFile = localFile;
                ContentType = contentType;
            }
        }
        #endregion
    }    
}